package com.example.bootintegration.datasource;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
import org.springframework.data.repository.config.RepositoryConfigurationDelegate;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;


import javax.sql.DataSource;
import java.util.Set;

/**
 * @author Quintoy
 * @date 12/30/22
 */
public class JpaBeansRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Set<String> dataSourceName = DataSourceRegistrar.registerIfAbsent(registry);
        dataSourceName.forEach(name -> {
            BeanDefinition beanDefinition = registry.getBeanDefinition(name + "JdbcTemplate");
            ConstructorArgumentValues.ValueHolder value = beanDefinition.getConstructorArgumentValues().getIndexedArgumentValue(0, DataSource.class);
            DataSource datasouce = (DataSource) value.getValue();
            registerBeans(name, datasouce, registry);
        });
    }


    private void registerBeans(String name, DataSource dataSource, BeanDefinitionRegistry registry) {
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)registry;
        ConfigurableEnvironment env = beanFactory.getBean(ConfigurableEnvironment.class);
        // 会主动进行初始化实例对象
        JpaFactory jpaFactory = beanFactory.getBean(JpaFactory.class);
        LocalContainerEntityManagerFactoryBean emf = jpaFactory.entityManagerFactory(name, dataSource);
        emf.afterPropertiesSet();
        beanFactory.registerBeanDefinition(name + "EntityManagerFactory", BeanDefinitionBuilder.rootBeanDefinition(DelegateFactoryBean.class).addConstructorArgValue(emf).getBeanDefinition());
        beanFactory.registerSingleton(name + "EntityManagerFactory", emf);
        // addConstructorArgReference,  解析时会自动调用 getBean() 获取参数对应的bean对象
        beanFactory.registerBeanDefinition(name + "TransactionManager", BeanDefinitionBuilder.rootBeanDefinition(JpaTransactionManager.class).addConstructorArgReference(name + "EntityManagerFactory").getBeanDefinition());
        this.registerRepositories(name, registry, env);
    }

    private void registerRepositories(String dataSourceName, BeanDefinitionRegistry registry, ConfigurableEnvironment env) {
        JpaRepositoryAnnotationMetadata metadata = new JpaRepositoryAnnotationMetadata(dataSourceName, env);
        JpaRepositoryConfigurationSource source = new JpaRepositoryConfigurationSource(dataSourceName, metadata, this.resourceLoader, env, registry);
        RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(source, this.resourceLoader, env);
        // 会扫描repository包中类, 会使用metadata中的属性来设置JpaRepositoryFactoryBean中的属性或者RepositoryFactoryBeanSupport 中的属性
        delegate.registerRepositoriesIn(registry, new JpaRepositoryConfigExtension());
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}
